#ifndef INC_MODEL_ATOM_ELEMENT_INDEXER_H
#define INC_MODEL_ATOM_ELEMENT_INDEXER_H

#include <map>
#include <list>

class Atom;
class Atom_Element;
class Molecule_Visitor;

// TODO: Leave just a fwd dcl
#include "Atom.h"
#include "Atom_Element.h"
#include "Molecule_Visitor.h"

/**
 * This object lives inside a molecule and knows the relationship between an
 * atom and its type and between a type and its atoms.
 *
 * Some times you need to know to which type an atom belongs, yet some times
 * you need to iterate all known atoms (in a molecule) by their type. This
 * class exists to avoid a circular dependency.
 */
class AtomElementIndexer
{
    typedef std::map< const std::string, Atom_Element* > Elements_By_Symbol;
    typedef std::map< const Atom*, Atom_Element* > Elements_By_Atom;
    typedef std::map< Atom_Element*, std::list<const Atom*>  > Atoms_By_Element;

    Elements_By_Symbol elements_by_symbol;
    Elements_By_Atom elements_by_atom;
    Atoms_By_Element atoms_by_element;

public:
    ~AtomElementIndexer()
    {
        // TODO: delete elements
    }

    void add(const Atom* atom, const Atom_Element& atom_elm)
    {
        // Add the type to our dictionary of types
        Atom_Element* atom_element = get_or_add(atom_elm);

        elements_by_symbol[ atom_element->symbol ] = atom_element;
        elements_by_atom[ atom ] = atom_element;
        atoms_by_element[ atom_element ].push_back(atom);
    }

    const Atom_Element* get_element_of(const Atom* at) const
    {
        return elements_by_atom.find(at)->second;
    }

    void iterate_atoms_by_type(Molecule_Visitor& v) const
    {
        for (Atoms_By_Element::const_iterator i = atoms_by_element.begin();
                i != atoms_by_element.end(); ++i)
        {
            const Atom_Element* type = i->first;
            v.on_type_found(type);

            for (std::list<const Atom*>::const_iterator j = i->second.begin();
                    j != i->second.end(); ++j)
            {
                const Atom* atom = *j;
                v.on_atom_found(atom, type);
            }
        }
    }

private:
    Atom_Element* get_or_add(const Atom_Element& type)
    {
        Atom_Element* a_type = elements_by_symbol[ type.symbol ];

        // If we haven't seen this type yet, add it
        if (!a_type)
        {
            a_type = type.clone();
            elements_by_symbol[ a_type->symbol ] = a_type;
        }

        return a_type;
    }
};

#endif /* INC_MODEL_FRAGMENT_H */
